Terraform で AWS Certificate Manager 無料証明書を発行する(AWS Provider 3.0.0 以降の場合)
先日、Terraform で AWS Certificate Manager(以下、ACM) の無料証明書をリクエストおよび検証まで実装する機会がありました。ちょっと詰まったところがありましたので同じように悩んでココへ辿り着く誰かのためにまとめておきます。
AWS Provider 3.0.0 から仕様変更されています
今回の記事は AWS Provider 3.0.0
以降を対象とします。今回の実行環境は以下のとおりです。
$ terraform version Terraform v0.12.29 + provider.aws v3.4.
AWS Provider 2.x
以前は以下のエントリーを参考にしてください。
Error: Invalid index
従来は aws_acm_certificate
で作成した無料証明書の検証用レコードは list
タイプで domain_validation_options
を返していました。そのため、以下のようにカウントインデックスをつけて aws_route53_record
で検証用レコードを登録しています。
resource aws_route53_record cert_validation { zone_id = data.aws_route53_zone.route53-zone.zone_id name = aws_acm_certificate.cert.domain_validation_options[0].resource_record_name type = aws_acm_certificate.cert.domain_validation_options[0].resource_record_type records = [aws_acm_certificate.cert.domain_validation_options[0].resource_record_value] ttl = 60 }
しかし、AWS Provider 3.0.0
以降で上記のように作成すると、以下のようなエラーになります。
Error: Invalid index on route53.tf line 42, in resource "aws_route53_record" "cert_validation": 42: name = aws_acm_certificate.cert.domain_validation_options[0].resource_record_name This value does not have any indices.
list から set へ変更
domain_validation_options
は以下のような形で返されます。ぱっと見た感じ list
タイプなのですが、3.0.0
以降では set
タイプに変更されています。
resource "aws_acm_certificate" "cert" { arn = "arn:aws:acm:ap-northeast-1:123456789012:certificate/46c469ae-8871-4b7f-a322-6f78005464f7" domain_name = "acm.marumo.classmethod.info" domain_validation_options = [ { domain_name = "*.acm.marumo.classmethod.info" resource_record_name = "_061e7ba0179d76ac63bcd370c87fb987.acm.marumo.classmethod.info." resource_record_type = "CNAME" resource_record_value = "_47bf1202c836b55e68832c5933fa5d3a.zdxcnfdgtt.acm-validations.aws." }, { domain_name = "acm.marumo.classmethod.info" resource_record_name = "_061e7ba0179d76ac63bcd370c87fb987.acm.marumo.classmethod.info." resource_record_type = "CNAME" resource_record_value = "_47bf1202c836b55e68832c5933fa5d3a.zdxcnfdgtt.acm-validations.aws." }, ] id = "arn:aws:acm:ap-northeast-1:123456789012:certificate/46c469ae-8871-4b7f-a322-6f78005464f7" status = "ISSUED" subject_alternative_names = [ "*.acm.marumo.classmethod.info", ] tags = {} validation_emails = [] validation_method = "DNS" options { certificate_transparency_logging_preference = "ENABLED" } }
set
は list
とは異なり順序付けされません。よって、以前のようなカウントインデックスは割り当てられず Error: Invalid index
となるわけです。
余談ですが list
から set
に変更された経緯は、list
の場合 terraform apply
が完了するまで、そのカウントインデックスが定まらず、属性を直接参照するような場合にエラーになる可能性があった、とのことです。詳しくは以下の issues に記載されていますので、興味があれば参照ください。
3.0.0 以降ではこうやる!
カウントインデックスを持たない set
タイプのため for/for_each
を利用します。コードは下記のとおりです。
data "aws_route53_zone" "domain" { name = "acm.marumo.classmethod.info" private_zone = false } resource "aws_acm_certificate" "cert" { domain_name = "acm.marumo.classmethod.info" subject_alternative_names = ["*.acm.marumo.classmethod.info"] validation_method = "DNS" } resource "aws_route53_record" "cert_validation" { for_each = { for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => { name = dvo.resource_record_name record = dvo.resource_record_value type = dvo.resource_record_type } } allow_overwrite = true name = each.value.name records = [each.value.record] ttl = 60 type = each.value.type zone_id = data.aws_route53_zone.domain.zone_id } resource "aws_acm_certificate_validation" "cert" { certificate_arn = aws_acm_certificate.cert.arn validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn] }
解説
3.0.0
以降で変更となった aws_route53_record
部分のみ取り上げて解説したいと思います。その他リソースの解説は、以前のかずえの記事に記載されていますので、そちらを参照ください。
aws_route53_record
まず for_each
の部分で何をやっているか説明します。
for_each = { for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => { name = dvo.resource_record_name record = dvo.resource_record_value type = dvo.resource_record_type } }
ここでは冒頭 domain_validation_options
の値で記載している 2 つの set
タイプのオブジェクトを以下のように domain_name
を key
とした map
タイプに変換しています。
$ terraform console > { for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => { name = dvo.resource_record_name, record = dvo.resource_record_value, type = dvo.resource_record_type }} { "*.acm.marumo.classmethod.info" = { "name" = "_061e7ba0179d76ac63bcd370c87fb987.acm.marumo.classmethod.info." "record" = "_47bf1202c836b55e68832c5933fa5d3a.zdxcnfdgtt.acm-validations.aws." "type" = "CNAME" } "acm.marumo.classmethod.info" = { "name" = "_061e7ba0179d76ac63bcd370c87fb987.acm.marumo.classmethod.info." "record" = "_47bf1202c836b55e68832c5933fa5d3a.zdxcnfdgtt.acm-validations.aws." "type" = "CNAME" } }
次に、上記のように map
変換された値を使い、以下で *.acm.marumo.classmethod.info
および acm.marumo.classmethod.info
を each.key
とする 各 each.value
の値を参照する形となっています。
allow_overwrite = true name = each.value.name records = [each.value.record] ttl = 60 type = each.value.type zone_id = data.aws_route53_zone.domain.zone_id }
このように記述することで、AWS Provider 3.0.0
以降の環境で無事に ACM 証明書の発行を Terraform で実装することができました!
さいごに
従来のカウントインデックスの方法で記述していたところエラーになりハマってしまいました。「実績のある方法で書いてるのに、なんでや・・・」と思っていたのですが、まさか domain_validation_options
が list
タイプから set
タイプに変更されていたとは。。
公式ドキュメントではすでに for_each
の記述方式に変わっていましたので、「まず、公式ドキュメント読め!」事案でした。
以上!大阪オフィスの丸毛(@marumo1981)でした!